package com.roamer.school.shiro.config;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.roamer.school.common.Constants;
import com.roamer.school.repository.entity.sys.SysResource;
import com.roamer.school.repository.entity.sys.SysUser;
import com.roamer.school.repository.enums.UserStatusEnum;
import com.roamer.school.repository.mapper.sys.MenuMapper;
import com.roamer.school.repository.mapper.sys.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * 自定义权限匹配和账户匹配
 *
 * @author roamer
 * @version V1.0
 * @date 2018/12/26 21:14
 */
@Slf4j
public class MyShiroRealm extends AuthorizingRealm {

    @Resource
    private UserMapper userMapper;

    @Resource
    private MenuMapper menuMapper;

    /**
     * 自定义权限匹配
     * <p>
     * 授权只能在成功的认证之后执行，因为授权数据（角色、权限等）必须总是与已知的标识相关联。这样的已知身份只能在成功登录时获得。
     * </p>
     *
     * @param principals 身份
     *
     * @return 角色和其拥有的权限
     *
     * @throws AuthorizationException 子类：
     *                                <ul>
     *                                <li>{@link org.apache.shiro.authz.UnauthorizedException} 抛出以指示请求的操作或对请求的资源的访问是不允许的</li>
     *                                <li>{@link org.apache.shiro.authz.UnauthenticatedException} 当尚未完成成功认证时，尝试执行授权操作时引发异常</li>
     *                                </ul>
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) throws AuthorizationException {
        // 获取用户信息
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        Long userId = user.getId();
        List<String> permsList;
        //系统管理员，拥有最高权限
        if (userId == Constants.SUPER_ADMIN) {
            // 获取所有权限
            List<SysResource> menuList = menuMapper.selectList(null);
            permsList = menuList.stream().map(SysResource::getPerms).collect(Collectors.toList());
        } else {
            // 获取权限列表
            permsList = menuMapper.queryAllPerms(userId);
        }

        //用户权限列表
        Set<String> permsSet = permsList.stream().filter(StringUtils::isNotBlank)
                                        .flatMap(perms -> Arrays.stream(perms.trim().split(",")))
                                        .collect(Collectors.toSet());

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setStringPermissions(permsSet);
        return authorizationInfo;
    }

    /**
     * 自定义账户匹配
     *
     * @param token 用户传入登陆凭证
     *
     * @return 数据库中的登陆凭证
     *
     * @throws AuthenticationException <ul>子类：
     *                                 <li>{@link org.apache.shiro.authc.CredentialsException} 凭证异常
     *                                 <ul>
     *                                 <li>{@link org.apache.shiro.authc.IncorrectCredentialsException} 不正确的凭证</li>
     *                                 <li>{@link org.apache.shiro.authc.ExpiredCredentialsException} 凭证过期</li>
     *                                 </ul>
     *                                 </li>
     *                                 <li>{@link org.apache.shiro.authc.AccountException} 账号异常
     *                                 <ul>
     *                                 <li>{@link org.apache.shiro.authc.ConcurrentAccessException} 并发访问异常（多个用户同时登录时抛出）</li>
     *                                 <li>{@link org.apache.shiro.authc.UnknownAccountException} 未知的账号</li>
     *                                 <li>{@link org.apache.shiro.authc.ExcessiveAttemptsException} 认证次数超过限制</li>
     *                                 <li>{@link org.apache.shiro.authc.DisabledAccountException} 禁用的账号</li>
     *                                 <li>{@link org.apache.shiro.authc.LockedAccountException} 账号被锁定</li>
     *                                 </ul>
     *                                 </li>
     *                                 <li>
     *                                 {@link org.apache.shiro.authc.pam.UnsupportedTokenException} 使用了不支持的Token</li>
     *                                 </li>
     *                                 </ul>
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取账号信息
        String username = (String) token.getPrincipal();
        log.info("Login user username = {}", username);
        Wrapper<SysUser> ew = new QueryWrapper<SysUser>().lambda().eq(SysUser::getUsername, username);
        SysUser user = userMapper.selectOne(ew);
        if (Objects.isNull(user)) {
            // 账号不存在
            throw new UnknownAccountException("账号不存在");
        }
        if (UserStatusEnum.LOCKED.equals(user.getStatus())) {
            // 账号被锁定
            throw new LockedAccountException("账号已被锁定,请联系管理员");
        }
        // SimpleAuthenticationInfo 将用户信息和密码封装为PrincipalCollection
        return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()),
                                            getName());
    }


}
